All files / web/src/app/api/curriculum/[playerId]/sessions/plans/[planId]/flow-events route.ts

0% Statements 0/82
0% Branches 0/1
0% Functions 0/1
0% Lines 0/82

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83                                                                                                                                                                     
import { NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/withAuth'
import { canPerformAction } from '@/lib/classroom'
import type { SessionPlan } from '@/db/schema/session-plans'
import { applySessionFlowEvent, StaleFlowVersionError } from '@/lib/curriculum/session-planner'
import { InvalidFlowTransitionError, type SessionFlowEvent } from '@/lib/curriculum/session-flow'
import { getUserId } from '@/lib/viewer'

function serializePlan(plan: SessionPlan) {
  return {
    ...plan,
    createdAt: plan.createdAt instanceof Date ? plan.createdAt.getTime() : plan.createdAt,
    approvedAt: plan.approvedAt instanceof Date ? plan.approvedAt.getTime() : plan.approvedAt,
    startedAt: plan.startedAt instanceof Date ? plan.startedAt.getTime() : plan.startedAt,
    completedAt: plan.completedAt instanceof Date ? plan.completedAt.getTime() : plan.completedAt,
    flowUpdatedAt:
      plan.flowUpdatedAt instanceof Date ? plan.flowUpdatedAt.getTime() : plan.flowUpdatedAt,
    breakStartedAt:
      plan.breakStartedAt instanceof Date ? plan.breakStartedAt.getTime() : plan.breakStartedAt,
  }
}

/**
 * POST /api/curriculum/[playerId]/sessions/plans/[planId]/flow-events
 * Apply a server-side flow event with optional optimistic concurrency.
 */
export const POST = withAuth(async (request, { params }) => {
  const { playerId, planId } = (await params) as { playerId: string; planId: string }

  try {
    const userId = await getUserId()
    const canModify = await canPerformAction(userId, playerId, 'start-session')
    if (!canModify) {
      return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
    }

    const body = await request.json()
    const { event, expectedFlowVersion } = body as {
      event?: SessionFlowEvent
      expectedFlowVersion?: number
    }

    if (!event?.type) {
      return NextResponse.json(
        {
          error: 'event is required',
          code: 'MISSING_EVENT',
        },
        { status: 400 }
      )
    }

    const plan = await applySessionFlowEvent(planId, event, expectedFlowVersion)
    return NextResponse.json({ plan: serializePlan(plan) })
  } catch (error) {
    if (error instanceof InvalidFlowTransitionError) {
      return NextResponse.json(
        {
          error: error.message,
          code: 'INVALID_FLOW_TRANSITION',
          flowState: error.state,
          eventType: error.eventType,
        },
        { status: 409 }
      )
    }
    if (error instanceof StaleFlowVersionError) {
      return NextResponse.json(
        {
          error: error.message,
          code: 'STALE_FLOW_VERSION',
          expectedFlowVersion: error.expectedFlowVersion,
          actualFlowVersion: error.actualFlowVersion,
        },
        { status: 409 }
      )
    }

    console.error('Error applying flow event:', error)
    return NextResponse.json({ error: 'Failed to apply flow event' }, { status: 500 })
  }
})